package file

import (
	"errors"
	"fmt"
	"image"
	"mime/multipart"
	"net/http"

	"gitee.com/dayu1985/framework/context"
	"gitee.com/dayu1985/framework/database/mongo"
	pimage "gitee.com/dayu1985/framework/storage/image"

	"crypto/md5"
	"encoding/hex"

	"image/gif"
	"image/jpeg"
	"image/png"

	"io"
	"os"

	mgo "gopkg.in/mgo.v2"
)

const SECTION_UPLOAD = "upload"

type LikeFile interface {
	io.ReadWriteSeeker
	io.Closer
}

type Uploader interface {
	Get(id string) (LikeFile, error)
	Resize(hex string, width int, height int) (io.ReadCloser, error)
	Upload(name string, contentType string, file multipart.File) (string, error)
	Size(name string) (width int, height int, err error)
}

type GridUploader struct {
	db *mgo.GridFS
}

var uploader Uploader

func NewUploader(ctx *context.Configuration) (Uploader, error) {

	uploader, err := ctx.BeanOf("uniplatform/framework/uploader", func() (interface{}, error) {

		if uploader != nil {
			return uploader, nil
		}

		up := ctx.INI.MustValue(SECTION_UPLOAD, "type")

		switch up {
		case "file":
			dir := ctx.INI.MustValue(SECTION_UPLOAD, "dir")
			uploader = NewFileUpload(dir)
			return uploader, nil
		case "mongodb":

			db := mongo.MustMongoDB(ctx, context.SECTION_DATABASE)
			uploader, err := NewMongoUploader(db)
			return uploader, err

		default:
			return nil, errors.New("[框架] 暂不支持的上传方式!")
		}
	})

	if err != nil {
		return nil, err
	}

	return uploader.(Uploader), err
}

func NewMongoUploader(db *mongo.Database) (Uploader, error) {

	g := &GridUploader{
		db: db.GridFS("fs"),
	}

	err := db.C("fs.files").EnsureIndexKey("filename")

	return g, err
}

func (g *GridUploader) Get(hex string) (LikeFile, error) {

	return g.db.OpenId(hex)
}

func (g *GridUploader) Resize(hex string, width int, height int) (io.ReadCloser, error) {

	sid := fmt.Sprintf("%s/%d/%d", hex, width, height)
	o, err := g.db.OpenId(sid)

	if err == nil {
		return o, nil
	}

	f, err := g.db.OpenId(hex)

	b := make([]byte, 512)
	f.Read(b)
	mime := http.DetectContentType(b)
	f.Seek(0, io.SeekStart)

	defer f.Close()

	if err != nil {
		return nil, err
	}

	var img image.Image

	img, _, err = image.Decode(f)

	if err != nil {
		return nil, err
	}

	m := pimage.ImageConvertor.Thumbnail(img, uint(width), uint(height))

	i, err := g.db.Create(sid)
	i.SetId(sid)

	if err != nil {
		return nil, err
	}

	switch mime {
	case "image/jpg", "image/jpeg":
		if err := jpeg.Encode(i, m, nil); err != nil {
			i.Close()
			return nil, err
		}
	case "image/png":
		if err := png.Encode(i, m); err != nil {
			i.Close()
			return nil, err
		}
	case "image/gif":
		if err := gif.Encode(i, m, nil); err != nil {
			i.Close()
			return nil, err
		}
	default:
		return nil, errors.New("未指定图片类型!")
	}

	i.Close()

	i, err = g.db.OpenId(i.Id())

	if err != nil {
		return nil, err
	}

	return i, nil
}

func (g *GridUploader) Upload(name string, contentType string, file multipart.File) (string, error) {

	hash := md5.New()
	io.Copy(hash, file)
	file.Seek(0, io.SeekStart)

	md5hash := hex.EncodeToString(hash.Sum([]byte("")))

	f, err := g.db.OpenId(md5hash)

	if err != nil && err != mgo.ErrNotFound {
		return "", err
	}

	if f != nil && f.Size() > 0 {
		defer f.Close()
		return md5hash, nil
	}

	f, err = g.db.Create(md5hash)

	f.SetId(md5hash)
	f.SetName(name)
	f.SetContentType(contentType)

	defer f.Close()

	if err != nil {
		return "", err
	}

	if _, err := io.Copy(f, file); err != nil {
		return "", err
	}

	return md5hash, nil
}

func (g *GridUploader) Size(hex string) (width int, height int, err error) {

	f, err := g.Get(hex)

	if err != nil {
		return 0, 0, err
	}

	defer f.Close()

	c, _, err := image.DecodeConfig(f)

	if err != nil {
		return
	}

	return c.Width, c.Height, err
}

func NewFileUpload(dir string) *FileUploader {

	return &FileUploader{
		dir: dir,
	}
}

type FileUploader struct {
	dir string
}

func (g *FileUploader) Get(hex string) (LikeFile, error) {

	name := fmt.Sprintf("%s/%s/%s/%s", g.dir, hex[0:1], hex[0:2], hex)
	return os.Open(name)
}

func (g *FileUploader) Resize(hex string, width int, height int) (io.ReadCloser, error) {

	sid := fmt.Sprintf("%s_%d_%d", hex, width, height)
	o, err := os.Open(sid)

	if err == nil {
		return o, nil
	}

	f, err := g.Get(hex)

	b := make([]byte, 512)
	f.Read(b)
	mime := http.DetectContentType(b)
	f.Seek(0, io.SeekStart)

	defer f.Close()

	if err != nil {
		return nil, err
	}

	img, _, err := image.Decode(f)

	if err != nil {
		return nil, err
	}

	m := pimage.ImageConvertor.Thumbnail(img, uint(width), uint(height))

	os.MkdirAll(fmt.Sprintf("%s/%s/%s", g.dir, sid[0:1], sid[0:2]), 0777)
	i, err := os.Create(fmt.Sprintf("%s/%s/%s/%s", g.dir, sid[0:1], sid[0:2], sid))

	if err != nil {
		return nil, err
	}

	switch mime {
	case "image/jpg", "image/jpeg":
		if err := jpeg.Encode(i, m, nil); err != nil {
			i.Close()
			return nil, err
		}
	case "image/png":
		if err := png.Encode(i, m); err != nil {
			i.Close()
			return nil, err
		}
	case "image/gif":
		if err := gif.Encode(i, m, nil); err != nil {
			i.Close()
			return nil, err
		}
	default:
		return nil, errors.New("未指定图片类型!")
	}

	i.Close()

	return g.Get(sid)
}

func (g *FileUploader) Upload(name string, contentType string, file multipart.File) (string, error) {

	hash := md5.New()
	io.Copy(hash, file)
	file.Seek(0, io.SeekStart)

	md5hash := hex.EncodeToString(hash.Sum([]byte("")))

	f, err := g.Get(md5hash)

	if f != nil && err == nil {
		return md5hash, err
	}

	os.MkdirAll(fmt.Sprintf("%s/%s/%s", g.dir, md5hash[0:1], md5hash[0:2]), 0777)
	f, err = os.Create(fmt.Sprintf("%s/%s/%s/%s", g.dir, md5hash[0:1], md5hash[0:2], md5hash))

	if err != nil {
		return md5hash, err
	}

	defer f.Close()

	if err != nil {
		return md5hash, err
	}

	if _, err := io.Copy(f, file); err != nil {
		return md5hash, err
	}

	return md5hash, nil
}

func (g *FileUploader) Size(hex string) (width int, height int, err error) {

	f, err := g.Get(hex)

	if err != nil {
		return 0, 0, err
	}

	defer f.Close()

	c, _, err := image.DecodeConfig(f)

	if err != nil {
		return
	}

	return c.Width, c.Height, err
}
